import datetime
import functools
import typing
import traceback

from coin.exchange.base.kr_rest.futures_product import (NativeFuturesProduct,
                                                        NormFuturesProduct,
                                                        parse_futures_product_symbol)
import coin.exchange.binance_futures.kr_rest.contract_util as contract_util
from coin.exchange.binance_futures.kr_rest.currency import BinanceCurrency
from coin.exchange.binance.kr_rest.constants import currency_list



class BinanceFuturesProduct(NativeFuturesProduct):
  _exchange = 'Binance'
  NativeCurrencyType = BinanceCurrency

  def __init__(self,
               base: NativeCurrencyType,
               quote: NativeCurrencyType,
               expiry: typing.Union[datetime.datetime, datetime.date, str],
               contract_type: str):
    if contract_type == 'perpetual':
      if quote.currency == 'USD':
        native_symbol = '%s%s_%s' % (base.native_currency, quote.currency, 'PERP')
      elif quote.currency in ('USDT', 'BUSD'):
        native_symbol = '%s%s' % (base.native_currency, quote.currency)
      else:
        raise ValueError(quote.currency)
    else:
      assert quote.currency in ('USD', 'USDT'), quote.currency
      expiry_str = expiry.strftime("%Y%m%d")[2:]
      native_symbol = '%s%s_%s' % (base.native_currency, quote.currency, expiry_str)

    super().__init__(base=base, quote=quote, expiry=expiry, native_symbol=native_symbol)
    self._contract_type = contract_type
    if contract_type != 'perpetual':
      self._subscription_symbol = '%s-%s.%s' % (
          base.currency, quote.currency, contract_type.upper())

  def __repr__(self):
    return str(super().__repr__(self.contract_type))

  @property
  def contract_type(self):
    return self._contract_type

  @classmethod
  @functools.lru_cache(maxsize=256)
  def FromProductImpl(cls, product, current_datetime):
    assert isinstance(product, NormFuturesProduct), type(product)
    assert product.quote.currency == "USDT"
    contract_type = contract_util.find_contract_type_from_expiry(current_datetime, product.expiry)
    return cls(product.base, product.quote, product.expiry, contract_type)

  @classmethod
  @functools.lru_cache(maxsize=256)
  def FromStrProductImpl(cls, product, current_datetime):
    assert isinstance(product, str), product

    parsed_result = parse_futures_product_symbol(product)
    assert parsed_result['quote'] in ('USD', 'USDT', 'BUSD'), parsed_result['quote']
    expiry_str = parsed_result['expiry'].lower()
    expiry, contract_type = contract_util.to_expiry_contract_type(expiry_str, current_datetime)

    base = cls.NativeCurrencyType.FromStrCurrency(parsed_result['base'])
    quote = cls.NativeCurrencyType.FromStrCurrency(parsed_result['quote'])
    return cls(base, quote, expiry, contract_type)

  @classmethod
  @functools.lru_cache(maxsize=256)
  def FromStrNativeProductImpl(cls, product, current_datetime):
    assert isinstance(product, str), product

    quote_candidates = ('BUSD', 'USDT')
    splitted = product.split('_')
    symbol = splitted[0]
    expiry_str = splitted[1] if len(splitted) == 2 else 'PERP'
    base = None
    quote = None
    errstr = ''
    try:
      for candidate in quote_candidates:
        if symbol.endswith(candidate):
          base = symbol[:-len(candidate)]
          quote = symbol[-len(candidate):]
          break
      assert base is not None
      assert quote is not None
      return cls.generate_product(base, quote, expiry_str, current_datetime)
    except Exception as e:
      errstr += traceback.format_exc()
    try:
      base = symbol[:-3]
      quote = symbol[-3:]
      assert quote == 'USD', quote
      return cls.generate_product(base, quote, expiry_str, current_datetime)
    except Exception as e:
      errstr += traceback.format_exc()
      raise ValueError(
          "Unable to parse: %s, %s, %s\n%s" % (product, current_datetime, cls, errstr))

  @classmethod
  @functools.lru_cache(maxsize=256)
  def generate_product(cls, base, quote, expiry_str, current_datetime):
    expiry, contract_type = contract_util.parse_native_expiry_contract_type(
        expiry_str, current_datetime)
    base = cls.NativeCurrencyType.FromStrNativeCurrency(base)
    quote = cls.NativeCurrencyType.FromStrNativeCurrency(quote)
    return cls(base, quote, expiry, contract_type)

  @classmethod
  @functools.lru_cache(maxsize=256)
  def FromCurrencyPair2AndContractTypeImpl(cls, base, quote, contract_type, current_datetime):
    expiry = contract_util.find_expiry_from_contract_type(current_datetime, contract_type)
    assert quote.currency == 'USDT', quote.currency
    return cls(base, quote, expiry, contract_type)
